<?php

namespace App\Services;

/**
 * iAjax 服务器端的 PHP 封装，配合 iAjax.js 进行使用。
 * 
 * @author Nio Xiao
 */
class Ajax
{
    private $params = [];
    private $errmsgs = [];
    
    /**
     * 当前操作是否为 AJAX 操作。
     *
     * @return boolean
     */
    public static function isAjax()
    {
        return ((!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'))
            || self::isAjaxUpload();
    }
    
    /**
     * 当前操作是否为 AJAX 上传文件。
     *
     * @return boolean
     */
    public static function isAjaxUpload()
    {
        return !empty($_FILES) && isset($_POST['AJAX_UPLOAD']) && $_POST['AJAX_UPLOAD'] == '1';
    }
    
    /**
     * 模拟当前操作为 AJAX 操作。
     */
    public static function mockAjax()
    {
        global $_SERVER;
        
        $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest';
    }
    
    /**
     * 输出 AJAX JSON 代码供客户端使用。
     *
     */
    public function display()
    {
        echo $this->__toString();
    }
    
    public function __toString()
    {
        if ($this->errmsgs) { //有错误，则清空所有其它 ajax actions，直接显示错误信息。
            $this->params = [];
            $this->showModal('错误信息', implode("\n<br/>\n", $this->errmsgs));
        }
        return json_encode(['actions' => $this->params]);
    }
    
    /**
     * 添加 AJAX 动作。
     *
     * @param string $action
     * @param array $params
     */
    private function _addAction($action, $params = [])
    {
        $this->params[] = array_merge(['type'=>$action], $params);
    }
    
    //------------------------- AJAX Actions -------------------------
    
    /**
     * 弹出 JS 警告框。
     *
     * @param string $content 警告内容。
     * @return iAjax
     */
    public function alert($content)
    {
        $this->_addAction('alert', ['content' => $content]);
        return $this;
    }
    
    /**
     * 刷新当前页面。
     *
     * @return iAjax
     */
    public function refresh()
    {
        $this->_addAction('refresh');
        return $this;
    }
    
    /**
     * 跳转到某一页面。
     *
     * @param string $url 跳转的目标页面 URL。
     * @return iAjax
     */
    public function forward($url)
    {
        $this->_addAction('forward', ['url' => $url]);
        return $this;
    }
    
    /**
     * 执行 javascript 代码。
     *
     * @param string $js
     * @return iAjax
     */
    public function js($js)
    {
        $this->_addAction('js', ['js' => $js]);
        return $this;
    }

    /**
     * 执行 javascript 回调函数。
     *
     * @param string $func javascript 回调函数名称。
     * @param mixed $data 传给回调函数的数据。
     * @return iAjax
     */    
    public function callback($func, $data = null)
    {
        $data = $data ? json_encode($data) : '';
        if (strpos($func, '(') !== false) {
            $js = $data ? preg_replace('#\(.+\)#', '', $func).'('.$data.')' : $func;
        } else {
            $js = $func.'('.$data.');';    
        }
        $this->js($js);
        return $this;
    }
    
    /**
     * 更新页面中的某一 DOM 元素。
     *
     * @param string $id 需要更新的 DOM 元素 ID。
     * @param string $html 用于更新的内容。
     * @param boolean $animate 是否动画显示。
     * @return iAjax
     */
    public function update($id, $html, $animate = false)
    {
        $this->_addAction('update', [
            'id'      => $id,
            'html'    => $html,
            'animate' => $animate
        ]);
        return $this;
    }
    
    /**
     * 替换页面中的某一 DOM 元素。
     *
     * @param string $id 需要替换的 DOM 元素 ID。
     * @param string $html 用于替换的内容。
     * @param boolean $animate 是否动画显示。
     * @return iAjax
     */
    public function replace($id, $html, $animate = false)
    {
        $this->_addAction('replace', [
            'id'      => $id,
            'html'    => $html,
            'animate' => $animate
        ]);
        return $this;
    }
    
    /**
     * 往页面中的某一 DOM 元素中增加内容。
     *
     * @param string $id 需要增加内容的 DOM 元素 ID。
     * @param string $html 用于增加的内容。
     * @param boolean $animate 是否使用动画效果。
     * @return iAjax
     */
    public function append($id, $html, $animate = false)
    {
        $this->_addAction('append', [
            'id'      => $id,
            'html'    => $html,
            'animate' => $animate,
        ]);
        return $this;    
    }
    
    /**
     * 显示提示信息。
     *
     * @param string $content 提示信息内容。
     * @param string $icon 提示图标，警告使用 alert，成功使用 ok。
     * @return iAjax
     */
    public function notice($content, $icon = 'alert')
    {
        $this->_addAction('notice', ['content' => $content, 'icon' => $icon]);
        return $this;
    }

    /**
     * 使用 modal 方式显示提示信息。
     *
     * @param string $content 提示信息内容。
     * @param string $icon 提示图标，警告使用 alert，成功使用 ok。
     * @param integer $timeout 提示信息显示的时长，单位为毫秒。
     * @return iAjax
     */
    public function showModalNotice($content, $icon = 'ok', $timeout = 2000)
    {
        $this->_addAction('showModalNotice', ['content' => $content, 'icon' => $icon, 'timeout' => $timeout]);
        return $this;
    }
    
    /**
     * 使用 lightbox 方式显示提示信息。
     *
     * @param string $content 提示信息内容。
     * @param string $title Lightbox 窗口标题。
     * @param integer $width 窗口宽度。
     * @param integer $height 窗口高度。
     * @param integer $top 窗口距离顶部的距离。
     * @return iAjax
     */
    public function modalNotice($content, $title = '提示信息', $width = null, $height = null, $top = 20)
    {
        $html = '<div class="ajax_congrats"><span class="icon"></span>'.$content.'</div>';
        return $this->showModal($title, $html, $width, $height, $top);
    }
    
    /**
     * 显示 lightbox 窗口。
     *
     * @param string $title Lightbox 窗口标题。
     * @param string $html 窗口中的内容。
     * @param integer $width 窗口宽度。
     * @param integer $height 窗口高度。
     * @param integer $top 窗口距离顶部的距离。
     * @param boolean $movable 窗口是否可以移动。
     * @return iAjax
     */
    public function showModal($title, $html, $width = null, $height = null, $top = null, $movable = true)
    {
        $this->_addAction('showModal', [
            'title'   => $title,
            'html'    => $html.'',
            'width'   => $width,
            'height'  => $height,
            'top'     => $top,
            'movable' => $movable,
        ]);
        return $this;
    }
    
    /**
     * 关闭 lightbox 窗口。
     *
     * @return iAjax
     */
    public function closeModal()
    {
        $this->_addAction('closeModal');
        return $this;
    }
    
    /**
     * 显示表单错误信息。
     *
     * @param array $errors 表单数据项错误数组，key 为表单数据项的 DOM id，value 为错误信息。
     * @return iAjax
     */
    public function formErrors($errors)
    {
        $js = '$(document).ready(function(){';
        foreach ($errors as $field => $error) {
            $js .= '_FieldError($("#'.$field.'"), "'.addslashes($error).'");';
        }
        $js .= '});';
        $this->js($js);
        return $this;
    }
    
    /**
     * 添加错误信息。
     *
     * @param string $errmsg 错误信息。
     * @return iAjax
     */
    public function addError($errmsg)
    {
        $this->errmsgs[] = $errmsg;
        return $this;
    }

    /**
     * 获取当前已有的params。
     *
     * @return Array
     */
    public function getParams() {
        return $this->params;
    }
    
    /**
     * 获取 ajax 调用的 JavaScript 代码。
     *
     * @param string $url Ajax url。
     * @param array $params Ajax post 参数数组。
     * @param boolean $escape 是否处理引号等。
     * @param string $history 用于浏览器历史记录的字符串，将直接加到 # 之后。
     * @return string 返回 ajax 调用代码。
     */
    public static function getJS($url, $params = [], $escape = true, $history = null)
    {
        if ($params) {
            $paramstr = json_encode($params);
            if ($escape) {
                $paramstr = self::quotes_to_entities($paramstr);
            }
        } else {
            $paramstr = '{}';
        }
        if ($history) {
            $js = "$.History.go('".$history."');";
        } else {
            $js = 'iAjax.add(\''.$url.'\', '.$paramstr.');';
        }
        return $js;
    }

    /**
     * 获取 Ajax 链接。
     *
     * @param string $text 链接的文字。
     * @param string $url Ajax url。
     * @param array $params Ajax post 参数数组。
     * @param array $attributes <a> 的属性数组。
     * @return string
     */
    public static function getLink($text, $url, $params = [], $attributes = [])
    {
        $confirm = isset($attributes['confirm'])
            ? self::quotes_to_entities($attributes['confirm'])
            : null;
        unset($attributes['confirm']);
        if (isset($attributes['history'])) {
            $history = $attributes['history'];
            unset($attributes['history']);
        } else {
            $history = null;
        }
        $ajax = self::getJS($url, $params, true, $history);
        $attributes['href'] = 'javascript:void(0)';
        $attributes['onclick'] = ($confirm)
            ? 'if (confirm(\''.$confirm.'\')) { '.$ajax.'; }'
            : $ajax;
        $attrs = [];
        foreach ($attributes as $k => $v) {
            $attrs[] = $k.'="'.$v.'"';
        }
        return '<a '.implode(' ', $attrs).'>'.$text.'</a>';
    }
    
    /**
     * 获取 AJAX 表单的开始标签 HTML 代码。
     * 
     * @param array $attributes <form> 的属性数组。
     * @return string
     */
    public static function getFormStart($attributes = []) 
    {
        if (isset($attributes['before_submit'])) {
            $onsubmit = 'if ('.$attributes['before_submit'].') { iAjax.submitForm(this); } ';
            unset($attributes['before_submit']);
        } else {
            $onsubmit = 'iAjax.submitForm(this);';
        }
        $onsubmit .= 'return false;';
        $default_attributes = [
            'name' => 'frm',
            'id' => 'frm',
            'method' => 'post',
            'action' => '',
            'onsubmit' => $onsubmit,
        ];
        foreach ($default_attributes as $k => $v) {
            if (!isset($attributes[$k])) $attributes[$k] = $v;
        }
        $attrs = [];
        foreach ($attributes as $k => $v) {
            $attrs[] = $k.'="'.$v.'"';
        }
        return '<form '.implode(' ', $attrs).'>';    
    }
    
    /**
     * 对字符串中的引号进行转义，使之可以出现在 HTML 标签属性中。
     *
     * @param string $str
     * @return string
     */
    public static function quotes_to_entities($str)
    {
        return str_replace(["\'","\"","'",'"'], ["&#39;","&quot;","&#39;","&quot;"], $str);
    }
}
